﻿using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Threading;
//using Cosmologica.MathInterface;
using Allegro.MathInterface;
using Allegro.MathLib.Functions;
//using Cosmologica.MathLib;

namespace Allegro.MathLib
{
    public class BesselFunctionLibraryContext
    {
        private ManualResetEvent _completionEvent;

        public BesselFunctionLibraryContext()
        {
            _completionEvent = new ManualResetEvent(false);
        }
        public void Complete()
        {
            _completionEvent.Set();
        }
        public void Wait()
        {
            _completionEvent.WaitOne();
        }
        public string BesselFileLocation;
        public Bessel BesselFunction;
        public int l;
        public int lMax;
        public double xMax;
        public int nPeriodSamples;
        public Spline BesselSpline;
    }

    public class FunctionLibrary
    {
        private static FunctionLibrary _instance = new FunctionLibrary();
        private ManualResetEvent _libraryLock = null;
        private SplinePool _besselLibrary;
        private string _functionLibraryLocation;

        /// <summary>
        /// Sub directory of the spherical Bessel files
        /// </summary>
        public const string SphericalBesselFileLocation = "SphericalBesselFunctions";

        private FunctionLibrary()
        {
            _libraryLock = new ManualResetEvent(false);
            _besselLibrary = new SplinePool("SphericalBesselFunctions");
            _libraryLock.Set();
        }

        static public FunctionLibrary Instance
        {
            get{ return _instance;}
        }

        public void RegisterFunctionLibraryLocation(string functionLibraryLocation)
        {
            _functionLibraryLocation = functionLibraryLocation;
        }
        public string FunctionLibraryLocation
        {
            get { return _functionLibraryLocation; }
        }
        /// <summary>
        /// Check out a Bessel function for index l. If it has not been loaded yet, it will be loaded
        /// </summary>
        /// <param name="l"></param>
        /// <returns></returns>
        public Spline CheckoutSphericalBesselFunction(int l)
        {
            string lKey = l.ToString();
            if(!_besselLibrary.HasSpline(lKey))
            {
                string besselFilePath = string.Format(@"{0}\{1}\{2}.bdat", _functionLibraryLocation, SphericalBesselFileLocation, l);
                Spline bessel = FunctionSerializer.Deserialize(besselFilePath, true);
                if (bessel != null)
                {
                    _besselLibrary.AddSplineData(lKey, bessel);
                }
                else
                    throw new ArgumentException(
                        string.Format("The function library contains no entry for Bessel function with l={0}", l));
            }
            return _besselLibrary.Checkout(lKey);
        }
        public void CheckinSphericalBesselFunction(int l, Spline besselFn)
        {
            _besselLibrary.Checkin(l.ToString(), besselFn);
        }

        /// <summary>
        /// Load a set of spherical Bessel functions from the function library
        /// </summary>
        /// <param name="lValues"></param>
        /// <returns></returns>
        public void LoadSphericalBesselFunctions(int[] lValues)
        {
            if(string.IsNullOrEmpty(_functionLibraryLocation))
                throw new ApplicationException("FunctionLibrary not initialized: Location of the function library not set");

            string besselFileDirectory =
               string.Format(@"{0}\{1}", _functionLibraryLocation, SphericalBesselFileLocation);
            List<BesselFunctionLibraryContext> contextList = new List<BesselFunctionLibraryContext>();
            Stopwatch timer = new Stopwatch();
            Console.WriteLine(string.Format("Starting loading of Bessel files at {0}", DateTime.Now));
            timer.Start();
         
            foreach(int l in lValues)
            {
                BesselFunctionLibraryContext context = new BesselFunctionLibraryContext();
                contextList.Add(context);
                context.BesselFileLocation = besselFileDirectory;
                context.l = l;

                ThreadPool.QueueUserWorkItem(_LoadSphericalBessel, context);
            }
            foreach (BesselFunctionLibraryContext context in contextList)
            {
                context.Wait();
                _besselLibrary.AddSplineData(context.l.ToString(), context.BesselSpline);
                Console.WriteLine(string.Format("Completed l = {0}", context.l));
            }
        }

        /// <summary>
        /// Generate spherical Bessel functions to file
        /// </summary>
        /// <param name="functionLibraryDirectory"></param>
        /// <param name="lMax"></param>
        /// <param name="xMax"></param>
        /// <param name="nPeriodSamples"></param>
        static public void GenerateSphericalBesselFiles(string functionLibraryDirectory, int lMax, double xMax, int nPeriodSamples)
        {
            string besselFileDirectory =
                string.Format(@"{0}\{1}", functionLibraryDirectory, SphericalBesselFileLocation);
            string lPathFormat = @"{0}\{1}.bdat";
            List<BesselFunctionLibraryContext> contextList = new List<BesselFunctionLibraryContext>();
            Stopwatch timer = new Stopwatch();
            Console.WriteLine(string.Format("Starting generation of Bessel files at {0}", DateTime.Now));
            timer.Start();
            for(int l = 0; l <= lMax; l++)
            {
                BesselFunctionLibraryContext context = new BesselFunctionLibraryContext();
                contextList.Add(context);
                context.BesselFileLocation = besselFileDirectory;
                context.nPeriodSamples = nPeriodSamples;
                context.lMax = lMax;
                context.xMax = xMax;
                context.BesselFunction = new Bessel();
                context.l = l;
                string lPath = string.Format(lPathFormat, besselFileDirectory, l);
                context.BesselFileLocation = lPath;
                Console.WriteLine(string.Format("Queuing generation of l = {0}", l));
                ThreadPool.QueueUserWorkItem(_GenerateSphericalBessel, context);
            }
            foreach(BesselFunctionLibraryContext context in contextList)
            {
                context.Wait();
                Console.WriteLine(string.Format("Completed l = {0}", context.l));
            }
            timer.Stop();
            Console.WriteLine(string.Format("Completed generation of Bessel files at {0}", DateTime.Now));
            Console.WriteLine(
                string.Format(
                    "Bessel functions from l=0 to l = {0} in {1} seconds ({2} minutes)", lMax,
                    timer.ElapsedMilliseconds / 1000, timer.Elapsed.Minutes));
        }

        // Private methods

        static void _LoadSphericalBessel(object context)
        {
            BesselFunctionLibraryContext c = context as BesselFunctionLibraryContext;
            string lPathFormat = @"{0}\{1}.bdat";
            string besselFilePath = string.Format(lPathFormat, c.BesselFileLocation, c.l);
            c.BesselSpline = FunctionSerializer.Deserialize(besselFilePath, true);
            c.Complete();
        }

        static void _GenerateSphericalBessel(object context)
        {
            BesselFunctionLibraryContext c = context as BesselFunctionLibraryContext;
            string besselFilePath = c.BesselFileLocation;
            double x0 = 0.0;
            double xMax = c.xMax;
            double nPeriodSamples = c.nPeriodSamples;
            double dx = 2.0 * Math.PI / nPeriodSamples;
            int l = c.l;

            Func<double, double> f = delegate(double x)
            {
                return c.BesselFunction.jl(l, x);
            };
            FunctionSerializer.Serialize(f, besselFilePath, x0, xMax, dx, true);
            c.Complete();
        }
        public static void GenerateSphericalBessel(string functionLibraryDirectory, int l, double x0, double xMax, double dx, bool binary)
        {
            string besselFilePath =
                string.Format(@"{0}\{1}\{2}.bdat", functionLibraryDirectory, SphericalBesselFileLocation, l);

            Bessel bessel = new Bessel();
            Func<double, double> f = delegate(double x)
            {
                return bessel.jl(l, x);
            };
            FunctionSerializer.Serialize(f, besselFilePath, x0, xMax, dx, true);
        }
    } 
}
